/**
 * Copyright (c) 2005-2012 https://github.com/zhangkaitao
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package com.tyf.common.dao.support;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.tyf.common.dao.BaseDao;
import com.tyf.common.dao.DaoHelper;
import com.tyf.common.dao.callback.SearchCallback;
import com.tyf.common.dao.support.annotation.QueryJoin;
import com.tyf.common.domain.search.SearchOperator;
import com.tyf.common.domain.search.Searchable;
import com.tyf.common.util.Collections3;
import com.tyf.common.util.CommonUtils;
import com.tyf.common.util.ReflectUtils;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.annotations.Formula;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import javax.persistence.*;
import javax.persistence.criteria.*;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.*;

/**
 * <p>
 * 抽象基础Custom Repository 实现
 * </p>
 * <p>
 * User: Zhang Kaitao && liutao
 * <p>
 * Date: 13-1-15 下午7:33
 * <p>
 * Version: 1.0
 */
public class SimpleBaseRepository<M, ID extends Serializable> extends SimpleJpaRepository<M, ID>
        implements BaseDao<M, ID> {

    public static final String LOGIC_DELETE_ALL_QUERY_STRING = "update %s x set x.deleted=true where x in (?1)";
    public static final String DELETE_ALL_QUERY_STRING = "delete from %s x where x in (?1)";
    public static final String FIND_QUERY_STRING = "from %s x where 1=1 ";
    public static final String COUNT_QUERY_STRING = "select count(x) from %s x where 1=1 ";
    public static final String UPDATE_SELECTIVE_QUERY_STRING = "update %s x set %s where 1=1 %s";
    public static final String DELETE_BY_PARMAS_QUERY_STRING = "delete from %s x where 1=1 %s";
    public static final String UPDATE_BY_PARAMS_QUERY_STRING = "update %s x set %s where 1=1 %s";
    public static final String FIND_SUM_QRUERY_STRING = "select %s from %s x where 1=1 ";


    private final EntityManager em;
    private final JpaEntityInformation<M, ID> entityInformation;

    private final DaoHelper repositoryHelper;

    private Class<M> entityClass;
    private String entityName;
    private String idName;

    private Iterable<String> idNames;

    /**
     * 查询所有的QL
     */
    private String findAllQL;
    /**
     * 统计QL
     */
    private String countAllQL;

    private SearchCallback searchCallback = SearchCallback.DEFAULT;

    private QueryJoin[] joins;

    // 选择性更新需忽略的属性
    private String[] updateIgnoreProperty = {"serialVersionUID", "class", "new", "id"};
    private Class<?>[] updateIgnoreAnnotations = {Transient.class, Formula.class, JoinColumn.class};

    /**
     * 实体分库字段
     */
    protected final String partitionBy;


    public SimpleBaseRepository(JpaEntityInformation<M, ID> entityInformation, EntityManager entityManager) {
        super(entityInformation, entityManager);

        this.entityInformation = entityInformation;
        this.entityClass = this.entityInformation.getJavaType();
        this.entityName = this.entityInformation.getEntityName();
        this.idName = this.entityInformation.getIdAttributeNames().iterator().next();
        this.idNames = entityInformation.getIdAttributeNames();
        this.em = entityManager;

        repositoryHelper = new DaoHelper(entityClass);

        findAllQL = String.format(FIND_QUERY_STRING, entityName);
        countAllQL = String.format(COUNT_QUERY_STRING, entityName);

        // 处理分库字段
        partitionBy = CommonUtils.getEntityPartitionBy(this.entityClass);
    }

    /**
     * 设置searchCallback
     *
     * @param searchCallback
     */
    public void setSearchCallback(SearchCallback searchCallback) {
        this.searchCallback = searchCallback;
    }

    /**
     * 设置查询所有的ql
     *
     * @param findAllQL
     */
    public void setFindAllQL(String findAllQL) {
        this.findAllQL = findAllQL;
    }

    /**
     * 设置统计的ql
     *
     * @param countAllQL
     */
    public void setCountAllQL(String countAllQL) {
        this.countAllQL = countAllQL;
    }

    /////////////////////////////////////////////////
    //////// 覆盖默认spring data jpa的实现////////////
    /////////////////////////////////////////////////

    /**
     * 根据主键删除相应实体
     *
     * @param id 主键
     */
    @Transactional
    @Override
    public void delete(final ID id) {
        M m = findOne(id);
        delete(m);
    }

    /**
     * 删除实体
     *
     * @param m 实体
     */
    @Transactional
    @Override
    public void delete(final M m) {
        super.delete(m);
    }

    /**
     * 根据主键删除相应实体
     *
     * @param ids 实体
     */
    @Transactional
    @Override
    public void delete(final ID[] ids) {
        if (ArrayUtils.isEmpty(ids)) {
            return;
        }
        List<M> models = new ArrayList<M>();
        for (ID id : ids) {
            M model = null;
            try {
                model = entityClass.newInstance();
            } catch (Exception e) {
                throw new RuntimeException("batch delete " + entityClass + " error", e);
            }
            try {
                BeanUtils.setProperty(model, idName, id);
            } catch (Exception e) {
                throw new RuntimeException("batch delete " + entityClass + " error, can not set id", e);
            }
            models.add(model);
        }
        deleteInBatch(models);
    }

    @SuppressWarnings("rawtypes")
    @Transactional
    @Override
    public void deleteInBatch(final Iterable<M> entities) {
        Iterator<M> iter = entities.iterator();
        if (entities == null || !iter.hasNext()) {
            return;
        }

        Set models = Sets.newHashSet(iter);

        String ql = String.format(DELETE_ALL_QUERY_STRING, entityName);
        repositoryHelper.batchUpdate(ql, models);
    }

    /**
     * 按照主键查询
     *
     * @param id 主键
     * @return 返回id对应的实体
     */
    @Transactional
    @Override
    public M findOne(ID id) {
        if (id == null) {
            return null;
        }
        if (id instanceof Integer && ((Integer) id).intValue() == 0) {
            return null;
        }
        if (id instanceof Long && ((Long) id).longValue() == 0L) {
            return null;
        }
        return super.findOne(id);
    }

    //////// 根据Specification查询
    //////// 直接从SimpleJpaRepository复制过来的///////////////////////////////////
    @Override
    public M findOne(Specification<M> spec) {
        try {
            return getQuery(spec, (Sort) null).getSingleResult();
        } catch (NoResultException e) {
            return null;
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.springframework.data.repository.CrudRepository#findAll(ID[])
     */
    public List<M> findAll(Iterable<ID> ids) {

        return getQuery(new Specification<M>() {
            public Predicate toPredicate(Root<M> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                Path<?> path = root.get(entityInformation.getIdAttribute());
                return path.in(cb.parameter(Iterable.class, "ids"));
            }
        }, (Sort) null).setParameter("ids", ids).getResultList();
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.springframework.data.jpa.repository.JpaSpecificationExecutor#findAll(
     * org.springframework.data.jpa.domain.Specification)
     */
    public List<M> findAll(Specification<M> spec) {
        return getQuery(spec, (Sort) null).getResultList();
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.springframework.data.jpa.repository.JpaSpecificationExecutor#findAll(
     * org.springframework.data.jpa.domain.Specification,
     * org.springframework.data.domain.Pageable)
     */
    public Page<M> findAll(Specification<M> spec, Pageable pageable) {

        TypedQuery<M> query = getQuery(spec, pageable);
        return pageable == null ? new PageImpl<M>(query.getResultList()) : readPage(query, pageable, spec);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.springframework.data.jpa.repository.JpaSpecificationExecutor#findAll(
     * org.springframework.data.jpa.domain.Specification,
     * org.springframework.data.domain.Sort)
     */
    public List<M> findAll(Specification<M> spec, Sort sort) {

        return getQuery(spec, sort).getResultList();
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * org.springframework.data.jpa.repository.JpaSpecificationExecutor#count(
     * org.springframework.data.jpa.domain.Specification)
     */
    public long count(Specification<M> spec) {

        return getCountQuery(spec).getSingleResult();
    }

    @Override
    public List<M> findAll() {
        return repositoryHelper.findAll(findAllQL);
    }

    @Override
    public List<M> findAll(final Sort sort) {
        return repositoryHelper.findAll(findAllQL, sort);
    }

    @Override
    public Page<M> findAll(final Pageable pageable) {
        return new PageImpl<M>(repositoryHelper.findAll(findAllQL, pageable), pageable,
                repositoryHelper.count(countAllQL));
    }

    @Override
    public long count() {
        return repositoryHelper.count(countAllQL);
    }

    /////////////////////////////////////////////////
    /////////////////// 自定义实现////////////////////
    /////////////////////////////////////////////////
    public void setJoins(QueryJoin[] joins) {
        this.joins = joins;
    }

    @Override
    public Page<M> findAll(final Searchable searchable) {
        List<M> list = repositoryHelper.findAll(findAllQL, searchable, searchCallback);
        long total = searchable.hasPageable() ? count(searchable) : list.size();
        return new PageImpl<M>(list, searchable.getPage(), total);
    }

    @Override
    public long count(final Searchable searchable) {
        return repositoryHelper.count(countAllQL, searchable, searchCallback);
    }

    /**
     * 重写默认的 这样可以走一级/二级缓存
     *
     * @param id
     * @return
     */
    @Override
    public boolean exists(ID id) {
        return findOne(id) != null;
    }

    /**
     * ======================eud-common===================
     **/
    @Override
    public M insert(M entity) {
        //TODO 将idNames设置为null
        em.persist(entity);
        return entity;
    }

    @Override
    public M update(M entity) {
        return em.merge(entity);
    }

    @Override
    public int updateSelective(M entity, String updateBy) {
        StringBuilder setQl = new StringBuilder(200);
        StringBuilder whereQl = new StringBuilder(100);
        List<Object> qlParams = Lists.newArrayList();

        //生成pudate set 部分的QL
        int paramIndex = genUpdateSetPartQl(entity, setQl, qlParams);

        //拼接where部分
        if (StringUtils.isNotEmpty(updateBy)) {
            String[] updateByArr = updateBy.split(",");
            int lastParamIndex = paramIndex;
            for (String s : updateByArr) {
                paramIndex = appendWhereQl(whereQl, s, entity, qlParams, paramIndex, null);
                if (lastParamIndex == paramIndex) {
                    whereQl.append("and " + s + " is null ");
                }
                lastParamIndex = paramIndex;
            }
        } else {
            appendDefaultWhereQl(whereQl, entity, qlParams, paramIndex);
        }

        // 拼接分片字段partitionBy条件
        appendPartitionByWhereQl(entity, whereQl, updateBy, qlParams);

        //执行
        String executeQl = String.format(UPDATE_SELECTIVE_QUERY_STRING, entityName, setQl.substring(1), whereQl.toString());
        return repositoryHelper.batchUpdate(executeQl, qlParams.toArray());
    }

    private int genUpdateSetPartQl(M entity, StringBuilder setQl, List<Object> qlParams) {
        int paramIndex = 1;
        if (entity == null) {
            return paramIndex;
        }
        try {
            // 拼接set部分
            Class type = entity.getClass();
            do {
                for (Field field : type.getDeclaredFields()) {
                    String fieldName = field.getName();
                    // 列不忽略更新且不为分库字段
                    // TODO: 这里需忽略new对象中的默认值,基本类型会有默认值0
                    if (!checkPropertyIsIgnored(field, fieldName)) {
                        ReflectUtils.makeAccessible(field);        //无视private/protected修饰符
                        Object result = field.get(entity);
                        if (result != null) {
                            setQl.append(", ").append(fieldName).append(" = (?").append(paramIndex).append(")");
                            qlParams.add(result);
                            paramIndex++;
                        }
                    }
                    ;
                }
                type = type.getSuperclass();
            } while (type != null);

        } catch (Exception e) {
            throw new RuntimeException(">>>>>>>>genUpdateSetPartQl " + entityClass + " error", e);
        }

        return paramIndex;
    }

    //检查更新的属性是否需要忽略
    private boolean checkPropertyIsIgnored(Field field, String propertyName) {
        // 分片字段忽略
        if (StringUtils.isNotEmpty(partitionBy) && propertyName.equals(partitionBy)) {
            return true;
        }

        field.getModifiers();
        // 公共忽略属性
        for (String s : updateIgnoreProperty) {
            if (propertyName.equals(s)) {
                return true;
            }
        }

        // JPA相关注解属性的忽略
        for (Class<?> c : updateIgnoreAnnotations) {
            if (field.isAnnotationPresent((Class<? extends Annotation>) c)) {
                return true;
            }
        }
        Column cloumnAnnotation = field.getAnnotation(Column.class);
        if (cloumnAnnotation != null && !cloumnAnnotation.updatable()) {//不需要更新的字段
            return true;
        }

        // 静态成员变量忽略
        if (field.toString().contains(" static ")) {
            return true;
        }

        return false;
    }

    /**
     * 构建whereQl && 设置Ql params
     *
     * @param whereQl
     * @param inputStr   条件值
     * @param entity     当前实体
     * @param paramIndex ql参数索引值
     * @return 返回当前paramIndex
     */
    private int appendWhereQl(StringBuilder whereQl, String inputStr, M entity, List<Object> qlParams, int paramIndex, Object inputValue) {
        String spType = "=";
        String propertyName = inputStr;
        int index = propertyName.indexOf("_");
        if (index > -1) {
            propertyName = inputStr.substring(0, index);
            spType = SearchOperator.valueOf(inputStr.substring(index + 1)).getSymbol();
        }
        if (entity != null) {
            inputValue = ReflectUtils.invokeGetter(entity, propertyName);
        }
        if (inputValue != null && !"".equals(inputValue.toString())) {
            whereQl.append(" and ").append(propertyName).append(" ").append(spType).append(" (");
            //处理数组或list元素
            if (inputValue instanceof List || inputValue.getClass().isArray()) {
                List list = null;
                String valueStr = "";
                if (inputValue instanceof List) {
                    list = (List) inputValue;
                } else {
                    list = Arrays.asList((Object[]) inputValue);
                }
                for (Object t : list) {
                    valueStr += ", ?" + (paramIndex++);
                }

                whereQl.append(valueStr.substring(1)).append(")");
                qlParams.addAll(list);
            } else {
                whereQl.append("?").append(paramIndex).append(")");
                qlParams.add(inputValue);
            }
            paramIndex++;
        }
        return paramIndex;
    }

    /**
     * 构建默认的Ql条件,以idName作为条件
     *
     * @param whereQl
     * @param entity
     * @param params
     * @param paramIndex
     */
    private void appendDefaultWhereQl(StringBuilder whereQl, M entity, List<Object> params, int paramIndex) {
        whereQl.append(" and ").append(idName).append(" = (?").append(paramIndex).append(")");
        Object idValue = ReflectUtils.invokeGetter(entity, idName);
        params.add(idValue);
    }

    @Override
    public int deleteByParams(Map<String, Object> params) {
        Assert.notEmpty(params, "deleteByParams method must have params!");

        StringBuilder whereQl = new StringBuilder(100);
        List<Object> qlParams = Lists.newArrayList();
        int paramIndex = 1;


        for (Map.Entry<String, Object> entry : params.entrySet()) {
            paramIndex = appendWhereQl(whereQl, entry.getKey(), null, qlParams, paramIndex, entry.getValue());
        }

        String executeQl = String.format(DELETE_BY_PARMAS_QUERY_STRING, entityName, whereQl.toString());
        if (executeQl.indexOf("and") == -1) {
            throw new IllegalArgumentException("current delete ql[" + executeQl + "] has no valid condition");    //当前delete sql语句没有有效的条件,不能删除
        }

        // TODO 无法自动拼接分片条件

        return repositoryHelper.batchUpdate(executeQl, qlParams.toArray());
    }

    @Override
    public int updateByParams(M entity, Map<String, Object> params) {
        return updateByParamsAndCustomSetQl(entity, params, null);
    }

    @Override
    public int updateForAutoIncrement(String propertyName, Map<String, Object> params) {
        return updateByParamsAndCustomSetQl(null, params, propertyName + "=" + propertyName + "+1");
    }

    @Override
    public int updateByParamsAndCustomSetQl(M entity, Map<String, Object> params, String customSetQl) {
        StringBuilder setQl = new StringBuilder(200);
        StringBuilder whereQl = new StringBuilder(100);
        List<Object> qlParams = Lists.newArrayList();

        // 生成pudate set 部分的QL
        int paramIndex = genUpdateSetPartQl(entity, setQl, qlParams);

        // 拼接传入的自定义的set 部分的QL
        if (StringUtils.isNotEmpty(customSetQl)) {
            if (setQl.length() == 0) {
                setQl.append(customSetQl);
            } else {
                setQl.append(", ").append(customSetQl);
            }
        }

        // 拼接where部分
        String whereByOther = "";
        if (Collections3.isNotEmpty(params)) {
            for (Map.Entry<String, Object> entry : params.entrySet()) {
                paramIndex = appendWhereQl(whereQl, entry.getKey(), null, qlParams, paramIndex, entry.getValue());
                whereByOther += "," + entry.getKey();
            }
            whereByOther = whereByOther.substring(1);
        } else {
            appendDefaultWhereQl(whereQl, entity, qlParams, paramIndex);
        }

        // 拼接分片字段partitionBy条件
        appendPartitionByWhereQl(entity, whereQl, whereByOther, qlParams);

        //执行
        String executeQl = String.format(UPDATE_BY_PARAMS_QUERY_STRING, entityName, setQl.substring(1), whereQl.toString());
        return repositoryHelper.batchUpdate(executeQl, qlParams.toArray());
    }

    @Override
    public int updateByIdNames(M entity, String updateByOther) {
        if (StringUtils.isNotEmpty(updateByOther)) {
            updateByOther += "," + StringUtils.join(idNames, ",");
        } else {
            updateByOther = StringUtils.join(idNames, ",");
        }
        return this.updateSelective(entity, updateByOther);
    }

    @Override
    public int deleteByIdNames(M entity, String deleteByOther) {
        StringBuilder whereQl = new StringBuilder(100);
        List<Object> qlParams = Lists.newArrayList();
        int paramIndex = 1;

        //拼接联合主键Where条件
        for (String s : idNames) {
            paramIndex = appendWhereQl(whereQl, s, entity, qlParams, paramIndex, null);
        }
        //拼接其他Where条件
        if (StringUtils.isNotEmpty(deleteByOther)) {
            String[] updateByArr = deleteByOther.split(",");
            for (String s : updateByArr) {
                paramIndex = appendWhereQl(whereQl, s, entity, qlParams, paramIndex, null);
            }
        }

        // 拼接分片字段partitionBy条件
        appendPartitionByWhereQl(entity, whereQl, deleteByOther, qlParams);

        String executeQl = String.format(DELETE_BY_PARMAS_QUERY_STRING, entityName, whereQl.toString());
        if (executeQl.indexOf("and") == -1) {
            throw new IllegalArgumentException("current delete ql[" + executeQl + "] has no valid condition");    //当前delete sql语句没有有效的条件,不能删除
        }
        return repositoryHelper.batchUpdate(executeQl, qlParams.toArray());
    }


    /**
     * 添加分片字段条件
     *
     * @param entity
     * @param whereQl
     * @param whereByOther
     * @param qlParams
     */
    public void appendPartitionByWhereQl(M entity, StringBuilder whereQl, String whereByOther, List<Object> qlParams) {
        // 如果where条件中已经包含了分片字段条件，则不添加分片条件，避免分片重复
        if (StringUtils.isEmpty(partitionBy) || whereByOther.startsWith(partitionBy) || whereByOther.contains("," + partitionBy)) {
            return;
        }
        Object partitionValue = null;
        try {
            partitionValue = ReflectUtils.invokeGetter(entity, partitionBy);
        } catch (Exception e) {
            throw new IllegalArgumentException("请指定实体类[" + entityClass + "]正确的分片字段,当前分片字段[" + partitionBy + "]在实体中不存在", e);
        }
//		CoreUtils.assertNotEmpty(partitionValue, "当前操作的实体["+ entityClass.getName() +"]分片字段[" + partitionBy + "]值不能为空");
        if (partitionValue == null || partitionValue.equals("")) {
            return;
        }

        whereQl.append("and " + partitionBy + " = (?" + (qlParams.size() + 1) + ")");
        qlParams.add(partitionValue);
    }

    @Override
    public void detach(M m) {
        em.detach(m);
    }

    @Override
    public Iterable<String> getIdNames() {
        return idNames;
    }

}
